package projects.android.sms.receiver;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;

import projects.android.sms.R;
import projects.android.sms.activities.EncryptMySMSActivity;
import projects.android.sms.activities.InboxActivity;
import projects.android.sms.db.DBHandle;
import projects.android.sms.encrypter.Encrypter;
import projects.android.sms.object.NumberHelper;
import projects.android.sms.object.SMS;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;

/**
 * Responsible for listening for incoming messages and detect the one associated with our app. The association is
 * detected with an appendix at the messages.
 * 
 * @author Thomas Jaeger
 * @author Mario Kraml
 * @author Tassilo Posegga
 * @author Robert Schenkenfelder
 * 
 */
public class SMSBroadcastReceiver extends BroadcastReceiver {

	public static final String SMS_EXTRA_NAME = "pdus";
	public static final String SMS_URI = "content://sms";
	public static final String ENCRYPT_TAG = "3ncryp7";

	public static final String ADDRESS = "address";
	public static final String PERSON = "person";
	public static final String DATE = "date";
	public static final String READ = "read";
	public static final String STATUS = "status";
	public static final String TYPE = "type";
	public static final String BODY = "body";
	public static final String SEEN = "seen";

	public static final int MESSAGE_TYPE_INBOX = 1;
	public static final int MESSAGE_TYPE_SENT = 2;

	public static final int MESSAGE_IS_NOT_READ = 0;
	public static final int MESSAGE_IS_READ = 1;

	public static final int MESSAGE_IS_NOT_SEEN = 0;
	public static final int MESSAGE_IS_SEEN = 1;

	public static final String PUBLIC_KEY_REQUEST_TAG = "You received a public key request for 3ncrypter. Please visit http://marketplace.app";
	public static final String PUBLIC_KEY_ANSWER_TAG = "4n5w3r";

	public static final String TAG = "SMSBroadcastReceiver";
	public static final int SMS_MAX_LENGTH = 159;

	private static String publicKey = "";
	private static int id = 0;

	@Override
	public void onReceive(Context context, Intent intent) {
		// Get SMS map from Intent
		Bundle extras = intent.getExtras();

		if (extras != null) {
			// Get received SMS array
			Object[] smsExtra = (Object[]) extras.get(SMS_EXTRA_NAME);

			for (int i = 0; i < smsExtra.length; ++i) {
				SmsMessage sms = SmsMessage.createFromPdu((byte[]) smsExtra[i]);

				String body = sms.getMessageBody().toString();
				String username = sms.getOriginatingAddress().toString();
				long date = sms.getTimestampMillis();
				DBHandle db = DBHandle.getInstance(context);

				// Check if the received message is a public key request
				if (body.contains(PUBLIC_KEY_REQUEST_TAG)) {
					Log.e(TAG, "Publiy key request incoming from " + username);

					RSAPublicKey encoded = db.getOwnPublicKey();
					SmsManager smsMan = SmsManager.getDefault();
					String msg = "mod:" + encoded.getModulus().toString(16);

					ArrayList<String> list = new ArrayList<String>();

					for (int j = 0; j < msg.length(); j += (SMS_MAX_LENGTH - PUBLIC_KEY_ANSWER_TAG.length())) {
						if (j + (SMS_MAX_LENGTH - PUBLIC_KEY_ANSWER_TAG.length()) > msg.length()) {
							list.add(msg.substring(j,
									j + msg.length() % (SMS_MAX_LENGTH - PUBLIC_KEY_ANSWER_TAG.length()))
									+ PUBLIC_KEY_ANSWER_TAG);
						} else {
							list.add(msg.substring(j, j + (SMS_MAX_LENGTH - PUBLIC_KEY_ANSWER_TAG.length()))
									+ PUBLIC_KEY_ANSWER_TAG);
						}
					}

					for (String string : list) {
						smsMan.sendTextMessage(username, null, string, null, null);
					}

					Log.e(TAG, "Sent my Public Key to " + username);
					// Do not store SMS into Android inbox
					this.abortBroadcast();
				}
				// Check if the received message is a public key answer
				else if (body.contains(PUBLIC_KEY_ANSWER_TAG)) {

					// delete tag
					body = body.substring(0, body.length() - PUBLIC_KEY_ANSWER_TAG.length());
					publicKey += body;
					Log.e(TAG, "Public key part received from " + username + "!\n" + body);

					// else :wait for other part
					if (publicKey.contains("mod") && publicKey.length() > body.length()) {
						Log.e(TAG, "second part arrived");
						// swap Strings when parts arrived in wrong order
						if (!publicKey.startsWith("mod")) {
							// check if the first part arrived first
							int index = publicKey.indexOf("mod");

							String newStart = publicKey.substring(index, publicKey.length());
							String newPublicKey = newStart + publicKey.substring(0, index);
							publicKey = newPublicKey;
						}

						// TODO check if saving public key worked

						// add received PublicKey to database
						try {
							RSAPublicKey pk = (RSAPublicKey) KeyFactory.getInstance(Encrypter.KEYPAIR_ALGORITHM)
									.generatePublic(
											new RSAPublicKeySpec(new BigInteger(publicKey.substring(4), 16), BigInteger
													.valueOf(Encrypter.PUBLIC_EXPONENT)));
							db.addPublicKey(username, pk);
							Toast.makeText(context, "Succesfully added a new public key from\n" + username,
									Toast.LENGTH_SHORT).show();
							Log.e(TAG, "Public key received from " + username + "!\n" + publicKey);
							notifyPublicKeyReceived(username, context);
						} catch (Exception e) {
							e.printStackTrace();
						}
						publicKey = "";
					}

					// Do not store SMS into Android inbox
					this.abortBroadcast();
				}
				// 3ncrypted SMS
				else if (body.contains(ENCRYPT_TAG)) {
					// delete tag
					body = body.substring(0, body.length() - ENCRYPT_TAG.length());

					// Save SMS into database
					SMS message = new SMS(body, username, date, true);
					Log.i(TAG, body);
					db.addMessage(message);
					db.close();
					notifySMSReceived(username, context);
					// TODO refresh inbox!
					// Do not store SMS into Android inbox
					this.abortBroadcast();
				}
			}
		}
	}

	/**
	 * Sends a notification that a new public key from user with username has been received.
	 * 
	 * @param username
	 *            The String representation of the users name/telephone number.
	 * @param context
	 *            The Application context.
	 */
	private void notifyPublicKeyReceived(String username, Context context) {
		NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
				.setSmallIcon(R.drawable.ic_launcher)
				.setContentTitle(
						"Public Key from " + NumberHelper.resolveNumberToName(context, username) + " received!")
				.setContentText(
						"You can now send encrypted SMS to " + NumberHelper.resolveNumberToName(context, username)
								+ ".").setAutoCancel(true);

		Intent resultIntent = new Intent(context, EncryptMySMSActivity.class);
		// artificial back stack for the started Activity.
		TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
		stackBuilder.addParentStack(InboxActivity.class);
		// add Intent that starts the Activity to the top of the stack
		stackBuilder.addNextIntent(resultIntent);
		PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
		builder.setContentIntent(resultPendingIntent);
		NotificationManager mNotificationManager = (NotificationManager) context
				.getSystemService(Context.NOTIFICATION_SERVICE);
		// id allows to update the notification later on.
		mNotificationManager.notify(id++, builder.build());
	}

	/**
	 * Sends a notification that a new public key from user with username has been received.
	 * 
	 * @param username
	 *            The String representation of the users name/telephone number.
	 * @param context
	 *            The Application context.
	 */
	private void notifySMSReceived(String username, Context context) {
		NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
				.setSmallIcon(R.drawable.ic_launcher)
				.setContentTitle("SMS from " + NumberHelper.resolveNumberToName(context, username) + " received!")
				.setContentText("You can decrypt the message in the app with your password.").setAutoCancel(true);

		Intent resultIntent = new Intent(context, InboxActivity.class);
		// artificial back stack for the started Activity.
		TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
		// add Intent that starts the Activity to the top of the stack
		stackBuilder.addNextIntent(resultIntent);
		PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
		builder.setContentIntent(resultPendingIntent);
		NotificationManager mNotificationManager = (NotificationManager) context
				.getSystemService(Context.NOTIFICATION_SERVICE);
		// id allows to update the notification later on.
		mNotificationManager.notify(id++, builder.build());
	}
}
